<?php
/**
 * Created by PhpStorm.
 * User: Inhere
 * Date: 2015/2/6
 * Use : 一个表的结构数据 (包含 Table Column)
 * File: Table.php
 */
namespace ulue\core\db\mysql\command;

use ulue\core\db\base\command\AbstractTable;
use ulue\core\db\mysql\query\SqlBuilder;
use ulue\core\db\base\schema\Column;
use ulue\core\db\base\schema\Schema;

class Table extends AbstractTable
{

    /* *
     * Property priKey.
     * @var  array
     */
    // protected $priKey;


//////////////////////////////////////////////// table info ////////////////////////////////////////////


    /**
     * @param bool $refresh
     * @return array
     */
    public function getDetail($refresh=false)
    {
        $detail = $this->db->getDatabase()->getTableDetail($this->name,$refresh);

        if ($this->schema) {
            $this->schema->engine           = $detail['Engine'];
            $this->schema->autoIncrement    = $detail['Auto_increment'];
            // $this->schema->defaultCharset = $detail['Charset'];
            $this->schema->collation        = $detail['Collation'];
            $this->schema->comment          = $detail['Comment'];
        }

        return $detail;
    }

    /**
     * @param bool $refresh
     * @return Schema
     */
    public function getSchema($refresh=false)
    {
        if ( !$this->schema || $refresh) {
            $schema = new Schema();

            $columns = $this->getColumnDetails();

            foreach($columns as $pos => $value) {
                $column = $this->loadColumnSchema($value,$pos+1);

                $schema->setColumn($column);

                if ( $value['Key'] ) {
                    $schema->indexes[$column->name]     = $value['Key'];
                }

                if ( $column->isPriKey ) {
                    $schema->priKey[]     = $column->name;
                }
            }

            $this->findConstraints($schema);

            $this->schema = $schema;

            //detail
            $this->getDetail();

            \Debug::trace("首次获取数据库[{$this->db->database}]中[{$this->name}]表的架构信息实例");
        }

        return $this->schema;
    }

    /**
     * @return \string[]
     */
    public function getPriKey()
    {
        return $this->getSchema()->priKey;
    }

    /**
     * @return array
     */
    public function getForeKey()
    {
        return $this->getSchema()->foreKey;
    }

    /**
     * @param $code
     * @return $this
     */
    public function setCreateCode($code)
    {
        if ($code && !is_string($code)) {
            $this->createCode = $code;
        }

        return $this;
    }

    /**
     * @return bool
     */
    public function getCreateCode()
    {
        if (!$this->createCode) {
            $sql   = SqlBuilder::showCreateTable($this->fullName);
            $row = $this->db->setSql($sql)->queryOne();

            if (isset($row['Create Table'])) {
                $code = $row['Create Table'];
            } else {
                $row  = array_values($row);
                $code = $row[1];
            }

            $this->createCode  = $code;
        }

        return $this->createCode;
    }

    /**
     * getIndexes
     *
     * @return  static
     */
    public function getIndexes()
    {
        $this->db->setSql('SHOW KEYS FROM ' . $this->db->quoteName($this->name))->execute();

        return $this->db->readAll();
    }

    /**
     * getIndexes
     * 查找约束
     * @param Schema $schema
     * @return  void
     */
    protected function findConstraints(Schema $schema)
    {
        $sql = $this->getCreateCode();

        $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi';

        if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
            foreach ($matches as $match) {
                $fks = array_map( 'trim', explode(',', str_replace('`', '', $match[1])) );
                $pks = array_map( 'trim', explode(',', str_replace('`', '', $match[3])) );
                $constraint = [str_replace('`', '', $match[2])];

                foreach ($fks as $k => $name) {
                    $constraint[$name] = $pks[$k];
                }

                //
                $schema->foreKey[] = $constraint;
            }
        }
    }

    /**
     * @return array
     */
    public function findUniqueIndexes()
    {
        $sql = $this->getCreateCode();
        $uniqueIndexes = [];

        $regexp = '/UNIQUE KEY\s+([^\(\s]+)\s*\(([^\(\)]+)\)/mi';

        if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
            foreach ($matches as $match) {
                $indexName      = str_replace('`', '', $match[1]);
                $indexColumns   = array_map('trim', explode(',', str_replace('`', '', $match[2])));

                $uniqueIndexes[$indexName] = $indexColumns;
            }
        }

        return $uniqueIndexes;
    }

//////////////////////////////////////////////// load Column ////////////////////////////////////////////

    /**
     * @param array $data
     * @param null $position
     * @return Column
     */
    public function loadColumnSchema(array $data, $position = null)
    {
        $column = new Column;

        $column->position = $position;
        $column->name     = $data['Field'];

        $this->parseType($data['Type'], $column);

        $column->collation = $data['Collation'];
        $column->allowNull = $data['Null']=='NO' ? false : true;

        $this->parseKey($data['Key'], $column);

        $column->default   = $data['Default'];

        if (stripos($data['Extra'], 'auto_increment') !== false) {
            $column->autoIncrement = true;
        }

        $column->privilege = $data['Privileges'];
        $column->comment   = $data['Comment'];

        return $column;
    }

    /**
     * @param $type
     * @param Column $column
     */
    public function parseType($type, Column $column)
    {
        $type = trim($type);

        // e.g. 'int(10) unsigned'
        if ( strpos($type, ' ') ) {
            list($type, $signed) = explode(' ', $type);
            $column->signed        = $signed=='signed' ? true : false;
        }

        // e.g. 'float'
        $column->type = $type;

        // e.g. 'int(10)' '/^(\w+)(?:\(([^\)]+)\))?/'
        if (preg_match("/^(\w+)(\(\d+\))/i", $type, $match)) {
            $column->type   = $match[1];
            $column->length = trim( $match[2] , '() ' );
        }
    }

    /**
     * @param $key
     * @param Column $column
     */
    public function parseKey($key, Column $column)
    {
        $column->key = $key;

        if ($key == 'PRI') {
            $column->isPriKey      = true;
            $column->allowNull     = false;
            $column->autoIncrement = true;
        }

    }



//////////////////////////////////////////////// column info ////////////////////////////////////////////

    public function getColumnNames($toString=false, $sep='`')
    {
        $columnNames = $this->getSchema()->getColumnNames();

        if ($toString) {
            return '`'.implode('`,`', $columnNames).'`';
        }

        return $columnNames;
    }


    /**
     * @paarms string $name
     * @return bool
     */
    public function existsColumn($name)
    {
        $columnNames = $this->getSchema()->getColumnNames();

        return in_array($name, $columnNames);
    }

    /**
     * @return array|\ulue\core\db\base\schema\Column[]
     */
    public function getColumns()
    {
        return $this->getSchema()->columns;
    }

    /**
     * @param bool $full
     * @return array|null
     */
    public function getColumnDetails($full = true)
    {
        $sql   = SqlBuilder::showTableColumns($this->fullName, $full);

        return $this->db->setSql($sql)->queryAll();
    }

    /**
     * @param $column
     * @param bool $full
     * @return mixed
     */
    public function getColumnDetail($column, $full = true)
    {
        $query = SqlBuilder::showTableColumns($this->fullName, $full, 'Field = ' . $this->db->quote($column));

        return $this->db->setSql($query)->queryOne();
    }

//////////////////////////////////////////////// table operate ////////////////////////////////////////////

    /**
     * create
     * @param bool  $ifNotExists
     * @param array $options
     * @return  static
     */
    public function create($ifNotExists = false, array $options = [])
    {
        return $this;
    }

    public function doCreate(
        $columns, array $pks = [], array $keys = [], $autoIncrement = null, $ifNotExists = true,
         $engine = 'InnoDB', $defaultCharset = 'utf8', $collate = 'utf8_general_ci'
    ){

    }

    /*
    public function updateByCreateCode() //create
    {
        return $this;
    }

    public function updateBySchema($ifNotExists = false, array $options = []) //create
    {
        return $this;
    }
    */
    // 更新表结构信息
    public function update()
    {
        $sql = 'ALTER TABLE '. $this->qn($this->name);
    }

    /**
     * save
     * @param bool  $ifNotExists
     * @param array $options
     * @return  static
     */
    public function save($ifNotExists = true, array $options = [])
    {
        if ($this->exists()) {
            $this->update();
        } else {
            $this->create($ifNotExists, $options);
        }
    }

    public function drop($ifNotExists = true, $option = '')
    {
        // $queryString = SqlBuilder::dropTable($this->name, $ifNotExists, $option);

        return $this;
    }

    /**
     * reset
     * @return  static
     */
    public function reset()
    {
        $this->columns = [];
        $this->primary = [];
        $this->indexes = [];

        return $this;
    }

    /**
     * @return bool
     */
    public function exists()
    {
        $database = $this->db->getDatabase();

        return $database->tableExists($this->name);
    }

    /**
     * @param $newName
     * @param bool $reload
     * @return $this|AbstractTable
     */
    public function rename($newName, $reload = true)
    {
        $this->db->setSql('RENAME TABLE ' . $this->qn($this->name) . ' TO ' . $this->qn($newName))->execute();

        if ($reload) {
            return $this->db->getTable($newName);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function lock()
    {
        $this->db->setSql('LOCK TABLES ' . $this->qn($this->name) . ' WRITE');//->execute();

        return $this;
    }

    /**
     * @return $this
     */
    public function unlock()
    {
        $this->db->setSql('UNLOCK TABLES')->execute();

        return $this;
    }

    /**
     * @return $this
     */
    public function truncate()
    {
        $this->db->setSql('TRUNCATE TABLE ' . $this->db->quoteName($this->name))->execute();

        return $this;
    }

//////////////////////////////////////////////// index info ////////////////////////////////////////////

    public function addIndex($type, $name = null, $columns = [], $comment = null, $options = [])
    {

        return $this;
    }

    /**
     * dropIndex
     * @param string  $type
     * @param string  $name
     * @return  static
     */
    public function dropIndex($type, $name)
    {

        return $this;
    }


//////////////////////////////////////////////// column operate ////////////////////////////////////////////


    /**
     * addColumn
     *
     * @param string $name
     * @param string $type
     * @param bool   $signed
     * @param bool   $allowNull
     * @param string $default
     * @param string $comment
     * @param array  $options
     *
     * @return  static
     */
    public function addColumn(
        $name, $type = 'text', $signed = true, $allowNull = true,
        $default = '', $comment = '',array $options = []
    ) {
        return $this;
    }

    /**
     * dropColumn
     * @param string $name
     * @return  mixed
     */
    public function dropColumn($name)
    {

    }


}// end class Table